﻿using System;
using System.Globalization;
using System.Linq;
using System.Management.Instrumentation;
using OpenTap.Plugins.Interfaces.Common;

namespace OpenTap.Plugins.Pm
{
    [Display("ROHDE&SCHWARZ NRP series PM driver", Group: "OpenTap.Plugins", Description: "ROHDE&SCHWARZ NRP series Power Meter driver")]
    public class PmRsNrpSxx : PmBase
    {
        public PmRsNrpSxx()
        {
            Name = "RsNrp8S";
        }

        /// <inheritdoc />
        public override void Open()
        {
            if (IsConnected)
                throw new InvalidOperationException("Pm is already connected!");
            base.Open();

            var tmp = IdnString;
            if ( !tmp.Contains("ROHDE&SCHWARZ,NRP8S") &&
                 !tmp.Contains("ROHDE&SCHWARZ,NRP40S") &&
                 !tmp.Contains("ROHDE&SCHWARZ,NRP50S"))
                throw new ApplicationException("Not supported instrument(" + tmp + "). Supported instruments are: ROHDE&SCHWARZ");

            // 1. run ONLY: self test, read limits
            if (MaxFrequencyHz == 0)
            {
                Log.Info("PM:SYSTem:VERSion: " + ScpiQuery("SYST:VERS?"));
                Log.Info("PM:ScpiInstrument IoTimeout = " + IoTimeout / 1000);

                var currIoTimeout = IoTimeout;
                if (currIoTimeout < 10000)
                {
                    IoTimeout = 10000;
                    Log.Info("Set IoTimeout to " + IoTimeout/1000 + "s for SelfTest.");
                }

                if (SelfTest())
                    throw new InstrumentationException("PM:Selftest FAIL");

                // Change IoTimeout back to original
                IoTimeout = currIoTimeout;

                Reset();
                Opc(TimeOutOpcTypicSec);
                QueryErrWithExc(true, "Reset()");

                var response = ScpiQuery("SYST:INFO? \"MAXFREQ\"\n");
                // Trim response for NRP8S. It gives e.g. "\"8e+09\"\n".
                response = response.TrimStart('\"');
                var index = response.LastIndexOf('\"');
                if (index > 0)
                    response = response.Substring(0, index);
                MaxFrequencyHz = long.Parse(response, NumberStyles.Float);
                Log.Info("PM MAX frequency(Hz): " + MaxFrequencyHz);

                response = ScpiQuery("SYST:INFO? \"MINFREQ\"\n");
                // Trim response for NRP8S
                response = response.TrimStart('\"');
                index = response.LastIndexOf('\"');
                if (index > 0)
                    response = response.Substring(0, index);
                MinFrequencyHz = long.Parse(response, NumberStyles.Float);
                Log.Info("PM MIN frequency(Hz): " + MinFrequencyHz);
            }
            else
            {
                Reset();
                Opc(TimeOutOpcTypicSec);
                QueryErrWithExc(true, "Reset()");
            }

            ScpiCommand("SENS:FUNC \"POW:AVG\"\n");
        }

        /// <inheritdoc />
        public override void ExecuteZeroCompensation()
        {
            Log.Debug("PM:ExecuteZeroCompensation start");
            ScpiCommand("CAL:ZERO:AUTO ONCE");
            Opc(TimeOutOpcTypicSec);
            Log.Debug("PM:ExecuteZeroCompensation done. Status(" + ScpiQuery("CAL:ZERO:AUTO?") + ")");
            QueryErrWithExc(true, "ExecuteZeroCompensation: CAL:ZERO:AUTO ONCE");

            var errors = QueryErrors(false, 10);
            if (errors.Count > 0)
            {
                string errorMsg = "PM don't execute Zeroing, Error:";
                for (int index = 0; index < errors.Count; index++)
                {
                    Log.Error("ERRor[" + errors[index].Code + "] " + errors[index].Message);
                    errorMsg = String.Concat(errorMsg, ", \n", errors[index].Code, " ", errors[index].Message);
                }
                throw new ApplicationException(errorMsg);
            }
        }

        public override double MeasureBurstPower(long frequencyKHz)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public override double MeasurePower(long frequencyKHz)
        {
            Log.Debug("PM:MeasurePower start");

            // Make Freq setting
            SetMeasureFrequency(frequencyKHz * 1000);
            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "MS:MeasurePower settings: " + frequencyKHz * 1000 + "Hz");

            // Start measuring,If IMM => start measuring immediately
            ScpiCommand("INIT:IMM\n");
            Opc(TimeOutOpcTypicSec);

            // Read result
            var respons = ScpiQuery("FETC?\n");
            Log.Debug("PM:MeasurePower response(" + respons + ")");
            QueryErrWithExc(true, "MS:MeasurePower meas");

            // Result conversion
            respons = respons.Substring(0,
                respons.IndexOf("\n", StringComparison.InvariantCulture) == -1
                    ? respons.Length
                    : respons.IndexOf("\n", StringComparison.InvariantCulture) + 1);

            double power = Double.NaN;
            if (respons.Split(',').Any(s => double.TryParse(s, NumberStyles.Float,
                    CultureInfo.InvariantCulture, out power)))
            {
                return ConvertWattsToDBm(power);
            }

            //conversion not successed
            throw new ApplicationException("PM:Measure power result conversion not succeeded");
        }

        public override void SetOffSetValue(EState state, double offsetDb)
        {
            Log.Info("Pm:SetOffSetValue(dBm): " + offsetDb);
            const int lowLimit = -200;
            const int highLimit = 200;

            if (-200 > offsetDb || 200 < offsetDb)
            {
                var errMsg = Scpi.Format("Offset value: {0} out of limits: {1} and {2}", offsetDb, lowLimit,
                    highLimit);
                throw new ApplicationException(errMsg);
            }

            ScpiCommand("SENS:CORR:OFFS " + offsetDb);
            ScpiCommand(state == EState.On ? "SENS:CORR:OFFS:STAT ON\n" : "SENS:CORR:OFFS:STAT OFF\n");

            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetOffSetValue(" + state + ", " + offsetDb + ")");
        }

        public override void SetAverageAuto()
        {
            Log.Info("Pm:Set Average Auto");

            ScpiCommand("SENS:AVER:COUN:AUTO ON");
            ScpiCommand("SENS:AVER:COUN:AUTO:TYPE NSR\n");
            //just using some value, for Ahca conversion..
            ScpiCommand("SENS:AVER:COUN:AUTO:NSR " + 1 + "\n");
            ScpiCommand("SENS:AVER:COUN:AUTO:MTIM " + 5 + "\n");


            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetAverageAuto()");
        }

        public override void SetAverageOff()
        {
            Log.Info("Pm:Set Average Auto");

            ScpiCommand("SENS:AVER:STAT OFF");

            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetAverageOff()");
        }

        public override void SetAverageManual(int averageCount)
        {
            if (averageCount < 1)
                throw new ArgumentException("PM:SetAverageManual Invalid parameter value(" + averageCount + ")", nameof(averageCount));

            Log.Info("Pm:Set Average Manual:" + averageCount);
            ScpiCommand("SENS:AVER:COUN:AUTO OFF\n");
            ScpiCommand("SENS:AVER:COUN " + averageCount + "\n");
            ScpiCommand("SENS:CORR:OFFS:STAT OFF\n");

            ScpiCommand(1 == averageCount ? "SENS:AVER:STAT OFF\n" : "SENS:AVER:STAT ON\n");

            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetAverageManual(" + averageCount + ")");
        }

        protected void SetMeasureFrequency(long frequencyHz)
        {
            if (MaxFrequencyHz < frequencyHz || MinFrequencyHz > frequencyHz)
                throw new ApplicationException("Frequency: " + frequencyHz + "Hz is out of range. Range is" +
                                               MinFrequencyHz +
                                               "Hz - " + MaxFrequencyHz + "Hz");
            ScpiCommand("SENS:FREQ " + frequencyHz + "\n");
        }

        private static double ConvertWattsToDBm(double watts)
        {
            if (watts <= 1E-13)
                watts = 1E-17;
            return 10.0 * Math.Log10(watts * 1000.0);
        }
    }
}
